今日筆者將來嘗試將【Day 14】資料庫中的資料以ListView,也就是滾動式的列表顯示在我們的使用者介面之中,並增加一個懸浮按鈕,以建立新的Todo並存入資料庫中。
懸浮按鈕的官方說明文件在此,而Scaffold中有著一項property: floatingActionButton,正是用於將懸浮按鈕加進我們的頁面之中。我們今天的目的之一便是建立一個懸浮按鈕,並且在點擊時能做出最基礎的Block物件。
懸浮按鈕將看起來像這樣。
p.s.在下面
來看程式吧!
之前的文章中,筆者的Todo頁面程式碼僅顯示了Text,而今天我們將更改此部份的內容。
以下是之前的程式碼:
// presentation/pages/todos.dart
// previous code: no floatingActionButton
class TodoPage extends StatelessWidget {
final TextStyle optionStyle;
const TodoPage(this.optionStyle, {super.key});
@override
Widget build(context) => Text('Todo', style: optionStyle);
}
下方則是增加了懸浮按鈕後的程式碼:
import 'package:flutter/material.dart';
class TodoPage extends StatelessWidget {
final TextStyle optionStyle;
TodoPage(this.optionStyle, {super.key});
int num = 0;
@override
Widget build(context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
// add new timeblock
onPressed: () {
db.insert(TimeBlock('name_$num'));
print('$num\n');
num++;
},
child: const Icon(Icons.add),
),
);
}
}
在以上程式中,筆者為了debug時的方便,將點擊懸浮按鈕後產生的block命名為name_[number],且新的block被儲存時,console會同時顯示出剛新增的編號。
在開始動工製作頁面前,先來將我們的class製作個大概的結構。不過首先,筆者想先談一下今日對於過去的程式碼所作的更動。
經過一番思考,筆者決定將原本於data/models/blocks.dart中的class更改為抽象類別,並利用OOP的概念,將另外製作兩個屬於BlocksDb的子類別: TimeBlocksDb
與 ScheduleBlocksDb
,並將TimeBlocks與ScheduleBlocks,也就是Todo與Schedule所新增的block分為兩個不同的class。
以下為data/models/blocks.dart
更改後的程式碼(部份相同的函式將省略內容):
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
abstract class Block {
final String name;
String? category = null, notes = null;
int? id = null;
Block(this.name);
Map<String, Object?> toMap();
}
abstract class BlocksDb {
String dbFilename, tableName, executeSQL;
bool dbIsOpen = false;
late final db;
BlocksDb(
{required this.dbFilename,
required this.tableName,
required this.executeSQL});
Future open() async {
db = openDatabase(
join(await getDatabasesPath(), dbFilename),
version: 1,
onCreate: (db, version) async {
return await db.execute('''CREATE TABLE $tableName ($executeSQL)''');
},
);
dbIsOpen = true;
}
Future<Block> insert(Block block) async {...}
Future<int> delete(int id) async {...}
Future<int> update(Block aBlock) async {...}
Future close() async {...}
Future<List<Map>> getAll() async {
if (!dbIsOpen) open();
return await db.rawQuery('SELECT * FROM $tableName');
}
Future<int> getCount() async =>
await db.execute('SELECT COUNT(*) from $tableName');
}
程式碼中,Block
類別所有的properties將會是TimeBlock
與ScheduleBlock
共同擁有的,而其餘這兩個類別的properties將不相同。也因為如此,在此將toMap()
改為抽象函式,交給子類別來定義。同理也將BlocksDb
在建構新的資料庫Table的SQL語言的程式碼更改為一項properties,可根據要建立的內容改動。
除此之外,筆者也增加了BlocksDb.getAll()
與BlocksDb.getCount()
這兩個函式,分別用於取得資料庫中的表格資料以及列數。
data/models/child_blocks.dart
是用於放Block
與BlocksDb
的子類別,其中將包含了TimeBlock
, TimeBlocksDb
, ScheduleBlock
, ScheduleBlocksDb
這四個子類別。今日將先製作TimeBlock
與TimeBlocksDb
的大致結構。
至於時間與日期,阿......之後有空再弄好啦!
// data/models/child_blocks.dart
import 'package:schedrag/data/models/blocks.dart';
class TimeBlock extends Block {
/* TODO: add time variables
* 1. estimated/spend time
* 2. deadline
* */
TimeBlock(super.name);
@override
Map<String, Object?> toMap() {
return {
'id': id,
'name': name,
'category': category,
//'estimatedTime': estimatedTime,
//'deadline': deadline,
'notes': notes,
};
}
}
class TimeBlocksDb extends BlocksDb {
static const String _executeSQL = '''
id INTEGER PRIMARY KEY,
name TEXT, category TEXT,
estimatedTime DATETIME,
deadline DATETIME,
notes TEXT''';
TimeBlocksDb()
: super(
dbFilename: 'timeBlock.db',
tableName: 'Todos',
executeSQL: _executeSQL);
}
可以由程式碼中看到,此檔案中含有原本在Blocks
的內容:toMap()
以及SQL程式碼。而由此方式,能夠更快速後續製作TimeBlocks與ScheduleBlocks,也更方便資料庫的管理。
就直接說吧,筆者最初以為只需要於BlocksDb
這個類別之中做一個能夠回傳資料庫資料的函數,並用for迴圈將他們一一做成ListTile顯示於應用程式介面上變能夠完工,結果發現的一個問題:The type 'Future\<List\<Map\<dynamic, dynamic>>>' used in the 'for' loop must implement 'Iterable'.
在Block
中,我們開啟或是處理資料庫中的內容都需要用到async
/await
,而這類型的函式回傳的物件將會是Future
,而Future
來自dart:async,並非dart:core的內容,因此無法使用迴圈iterate過其中的資料。故此部份程式碼無法編譯與執行。
以下為筆者原本為此部份寫的內容,因暫時還不知道該如何處理這個問題,因此這部份今日將無法完成,並推遲到後續的日子。
import 'package:flutter/material.dart';
import 'package:schedrag/data/models/child_blocks.dart';
class TodoPage extends StatelessWidget {
final TextStyle optionStyle;
final db = TimeBlocksDb();
TodoPage(this.optionStyle, {super.key});
int num = 0;
@override
//Widget build(BuildContext context) => Text('Todo', style: optionStyle);
Widget build(context) {
return Scaffold(
body: ListView(
children: [
for (var row in db.getAll())
ListTile(title: Text(row['name']), onTap: () {}),
],
),
floatingActionButton: FloatingActionButton(
// add new timeblock
onPressed: () {
db.insert(TimeBlock('name_$num'));
print('name_$num/n');
num++;
},
child: const Icon(Icons.add),
),
);
}
}
這篇文章就到這裡結束,謝謝閱讀到這裡的讀者~
有任何疑問或是想說的都歡迎留言或email!
最後是筆者的小內心戲:
阿阿阿阿阿阿阿阿阿阿阿阿阿怎麼跑不動!!!!跟原本想像的不同,突然遇到這個問題有點挫折。゚ヽ(゚´Д)ノ゚。 希望能夠找到解決方法( ´•̥̥̥ω•̥̥̥
)